Skip to content

Add clang-tidy static analysis for the NIF - #208

Merged
ausimian merged 2 commits into
mainfrom
claude/nif-static-analysis-tools-t3rmhg
Jul 20, 2026
Merged

Add clang-tidy static analysis for the NIF#208
ausimian merged 2 commits into
mainfrom
claude/nif-static-analysis-tools-t3rmhg

Conversation

@ausimian

@ausimian ausimian commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Wire clang-tidy — including the clang static analyzer, via its
clang-analyzer-* checks — over the C++ NIF sources in c_src/, following
the same shape as the cppcheck integration (make target + macOS CI job + docs).

The one real difference from cppcheck: clang-tidy compiles each translation
unit, so it needs the MLX/Fine/ERTS headers and the exact build flags rather
than running on a bare checkout.

What's here

  • .clang-tidy (repo root) — a focused, high-signal set (bugprone-*,
    clang-analyzer-*, performance-*), WarningsAsErrors: '*' to gate, and
    HeaderFilterRegex: c_src/ so diagnostics stay on our own code and never the
    -isystem MLX/Fine headers. Broaden the set once this baseline is settled.
  • clang-tidy Makefile target — reuses the NIF's exact $(CXXFLAGS) via
    the trailing --. Refuses to run without the build env (pointing at
    mix clang.tidy) instead of emitting a confusing empty--isystem error.
  • mix clang.tidy task — the developer entry point. Mirrors the existing
    mix bench.native: pulls the project's make_env, adds ERTS_INCLUDE_DIR,
    reuses the already-built/cached MLX, and drives the make target.
  • macOS CI jobbrew install llvm + mix clang.tidy, reusing the
    precommit lane's MLX cache via restore-keys (only headers are needed and
    the MLX version is unchanged, so no extra source build in the common case).
  • MAINTAINING.md — a clang-tidy entry alongside cppcheck.

First-run triage

No clang-analyzer-* findings (no latent bugs). The rest split into:

  • Checks disabled (systemic, framework-dictated or deliberate design),
    documented in .clang-tidy: performance-unnecessary-value-param (Fine's
    by-value FINE_NIF decode convention — the cppcheck equivalent of
    passedByValueCallback), bugprone-throwing-static-initialization
    (FINE_NIF/FINE_RESOURCE register at static-init), and
    performance-enum-size (Opcode/ref::Kind are int64_t for the packed-ref
    ABI).
  • Genuine findings fixed: renamed the reserved-identifier namespaces
    __async/__opasync_detail/op_detail; bound two window-reduce shapes
    by const& instead of copying; widened rank before 2 * rank reserves.
  • False positives suppressed inline with rationale: the length-counted
    BEAM-binary memcpy (not a C string), and the three best-effort cleanup
    destructors where a throw is already fatal (the check stays on elsewhere).

claude added 2 commits July 20, 2026 19:43
Wire clang-tidy (including the clang static analyzer, via its
clang-analyzer-* checks) over the C++ NIF sources in c_src/.

Unlike cppcheck, clang-tidy compiles each translation unit, so it needs
the MLX/Fine/ERTS headers and the exact build flags. Add a `clang-tidy`
Makefile target that reuses the NIF's $(CXXFLAGS), and a `mix clang.tidy`
task that supplies the same env elixir_make uses (reusing the cached MLX)
before invoking it — mirroring the existing `mix bench.native` pattern.
The make target refuses to run without that env rather than emit a
confusing empty-`-isystem` clang error.

Enabled checks and the header filter (diagnostics scoped to c_src/, never
the -isystem MLX/Fine headers) live in a repo-root .clang-tidy; start with
a focused bugprone/analyzer/performance set. Add a macOS CI job that
installs LLVM via brew and runs `mix clang.tidy`, reusing the precommit
lane's MLX cache via restore-keys. Document it in MAINTAINING.md.
Disable three checks that fire systemically on framework-dictated or
deliberate patterns, documented in .clang-tidy:
  - performance-unnecessary-value-param: every NIF takes its ResourcePtr
    args by value per Fine's FINE_NIF decode convention (the cppcheck
    build suppresses the same thing as passedByValueCallback).
  - bugprone-throwing-static-initialization: FINE_NIF/FINE_RESOURCE
    register at static-init via throwing ctors, across macro expansions.
  - performance-enum-size: Opcode and ref::Kind are int64_t on purpose to
    pack into the int64 refs the Elixir lowerer emits.

Fix the genuine findings:
  - Rename the reserved-identifier namespaces __async/__op (leading double
    underscore is reserved) to async_detail/op_detail.
  - Bind window-reduce shapes by const reference instead of copying
    (performance-unnecessary-copy-initialization), matching the existing
    style a few lines up.
  - Widen `rank` before the `2 * rank` reserve() so the multiplication
    happens in size_t (bugprone-implicit-widening-of-multiplication-result).

Suppress the remaining false positives inline, with rationale:
  - The make_binary_from_cstr memcpy targets a length-counted BEAM binary,
    not a C string (bugprone-not-null-terminated-result).
  - ~Program/~Reaper/~WorkerThread are best-effort cleanup destructors
    where a throw is already fatal (bugprone-exception-escape); the check
    stays enabled for every other function.
@ausimian
ausimian merged commit a1034ca into main Jul 20, 2026
5 checks passed
@ausimian
ausimian deleted the claude/nif-static-analysis-tools-t3rmhg branch July 20, 2026 20:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants